import torch
import torch.nn as nn


class Conv(nn.Module):
    def __init__(self, c1, c2, k=1, s=1, g=1):
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(in_channels=c1, out_channels=c2, kernel_size=k, stride=s, padding=k//2, groups=g)
        self.act = nn.SiLU()

    def forward(self, x):
        x = self.conv(x)
        return self.act(x)


class Focus(nn.Module):
    def __init__(self):
        super(Focus, self).__init__()
        self.conv = Conv(c1=12, c2=32, k=3, s=1)

    def forward(self, x):
        x = torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)
        return self.conv(x)


class Bottleneck(nn.Module):
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):
        super(Bottleneck, self).__init__()
        c_ = int(c2 * e)
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut

    def forward(self, x):
        identity = x
        x = self.cv1(x)
        x = self.cv2(x)
        if self.add:
            x += identity
        return x


class CSPconv(nn.Module):
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        super().__init__()
        c_ = int(c2 * e)
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        x1 = self.cv1(x)
        x2 = self.cv2(x)
        x = torch.cat([self.m(x1), x2], dim=1)
        return self.cv3(x)


class SPP(nn.Module):
    def __init__(self, c1, c2, k=(5, 9, 13)):
        super(SPP, self).__init__()
        c_ = c1 // 2
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
        self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

    def forward(self, x):
        x = self.cv1(x)
        x = torch.cat([x] + [m(x) for m in self.m], 1)
        return self.cv2(x)


class Classify(nn.Module):
    def __init__(self, c1, c2):
        super(Classify, self).__init__()
        self.conv = nn.Conv2d(in_channels=c1, out_channels=c2, kernel_size=1)
        self.aap = nn.AdaptiveAvgPool2d(1)

    def forward(self, x):
        z = torch.cat([self.aap(y) for y in x], 1)
        z = self.conv(z)
        return torch.flatten(z, 1)
